﻿namespace Nop.Plugin.Payments.UsaEpay
{
    using System;
    using System.Collections.Generic;
    using System.Web.Routing;

    using Nop.Core.Domain.Orders;
    using Nop.Core.Domain.Payments;
    using Nop.Core.Plugins;
    using Nop.Plugin.Payments.UsaEpay.Controllers;
    using Nop.Services.Configuration;
    using Nop.Services.Customers;
    using Nop.Services.Localization;
    using Nop.Services.Orders;
    using Nop.Services.Payments;

    /// <summary>
    ///     The USA ePay payment processor.
    /// </summary>
    public class UsaEpayPaymentProcessor : BasePlugin, IPaymentMethod
    {
        /// <summary>
        ///     The order total calculation service.
        /// </summary>
        private readonly IOrderTotalCalculationService orderTotalCalculationService;

        /// <summary>
        ///     The token.
        /// </summary>
        private readonly ueSecurityToken token;

        /// <summary>
        ///     The USA ePay payment settings.
        /// </summary>
        private readonly UsaEpayPaymentSettings usaEpayPaymentSettings;

        /// <summary>
        ///     The USA ePay service.
        /// </summary>
        private readonly IUsaEpayService usaEpayService;

        /// <summary>
        /// The setting service.
        /// </summary>
        private readonly ISettingService settingService;

        /// <summary>
        /// The customer service.
        /// </summary>
        private readonly ICustomerService customerService;

        /// <summary>
        /// Initializes a new instance of the <see cref="UsaEpayPaymentProcessor"/> class.
        /// </summary>
        /// <param name="orderTotalCalculationService">
        /// The order total calculation service.
        /// </param>
        /// <param name="settingService">
        /// The setting service.
        /// </param>
        /// <param name="usaEpayPaymentSettings">
        /// Payment settings.
        /// </param>
        /// <param name="usaEpayService">
        /// The USA ePay Service.
        /// </param>
        /// <param name="customerService">
        /// The customer Service.
        /// </param>
        public UsaEpayPaymentProcessor(IOrderTotalCalculationService orderTotalCalculationService, ISettingService settingService, UsaEpayPaymentSettings usaEpayPaymentSettings, IUsaEpayService usaEpayService, ICustomerService customerService)
        {
            this.PaymentMethodType = PaymentMethodType.Standard;
            this.RecurringPaymentType = RecurringPaymentType.NotSupported;
            this.SupportCapture = true;
            this.SupportPartiallyRefund = true;
            this.SupportRefund = true;
            this.SupportVoid = true;
            this.usaEpayPaymentSettings = usaEpayPaymentSettings;
            this.orderTotalCalculationService = orderTotalCalculationService;
            this.settingService = settingService;
            this.customerService = customerService;

            this.token = new UsaEpayToken(this.usaEpayPaymentSettings).Token;

            this.usaEpayService = usaEpayService;
            if (this.usaEpayPaymentSettings.Url != null)
            {
                this.usaEpayService.Url = this.usaEpayPaymentSettings.Url;
            }
        }

        /// <summary>
        ///     Gets the payment method type.
        /// </summary>
        public PaymentMethodType PaymentMethodType { get; private set; }

        /// <summary>
        /// Gets a value indicating whether we should display a payment information page for this plugin
        /// </summary>
        public bool SkipPaymentInfo
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        ///     Gets the recurring payment type.
        /// </summary>
        public RecurringPaymentType RecurringPaymentType { get; private set; }

        /// <summary>
        ///     Gets a value indicating whether support capture.
        /// </summary>
        public bool SupportCapture { get; private set; }

        /// <summary>
        ///     Gets a value indicating whether support partially refund.
        /// </summary>
        public bool SupportPartiallyRefund { get; private set; }

        /// <summary>
        ///     Gets a value indicating whether support refund.
        /// </summary>
        public bool SupportRefund { get; private set; }

        /// <summary>
        ///     Gets a value indicating whether support void.
        /// </summary>
        public bool SupportVoid { get; private set; }

        /// <summary>
        /// The can repost process payment.
        /// </summary>
        /// <param name="order">
        /// The order.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        public bool CanRePostProcessPayment(Order order)
        {
            return false;
        }

        /// <summary>
        /// The cancel recurring payment.
        /// </summary>
        /// <param name="cancelPaymentRequest">
        /// The cancel payment request.
        /// </param>
        /// <returns>
        /// The <see cref="CancelRecurringPaymentResult"/>.
        /// </returns>
        /// <exception cref="NotImplementedException">
        /// Not implemented at this time.
        /// </exception>
        public CancelRecurringPaymentResult CancelRecurringPayment(CancelRecurringPaymentRequest cancelPaymentRequest)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// The capture.
        /// </summary>
        /// <param name="capturePaymentRequest">
        /// The capture payment request.
        /// </param>
        /// <returns>
        /// The <see cref="CapturePaymentResult"/>.
        /// </returns>
        public CapturePaymentResult Capture(CapturePaymentRequest capturePaymentRequest)
        {
            var capturePaymentResult = new CapturePaymentResult
                                           {
                                               NewPaymentStatus =
                                                   capturePaymentRequest.Order.PaymentStatus
                                           };

            try
            {
                TransactionResponse transactionResponse = this.usaEpayService.captureTransaction(
                    this.token, 
                    capturePaymentRequest.Order.AuthorizationTransactionId, 
                    Convert.ToDouble(capturePaymentRequest.Order.OrderTotal));

                capturePaymentResult = TransactionResponseProcessor.ProcessCapture(transactionResponse, capturePaymentResult);
            }
            catch (Exception exception)
            {
                capturePaymentResult.AddError(exception.Message);
            }

            return capturePaymentResult;
        }

        /// <summary>
        /// The get additional handling fee.
        /// </summary>
        /// <param name="cart">
        /// The cart.
        /// </param>
        /// <returns>
        /// The <see cref="decimal"/>.
        /// </returns>
        public decimal GetAdditionalHandlingFee(IList<ShoppingCartItem> cart)
        {
            decimal result = this.CalculateAdditionalFee(
                this.orderTotalCalculationService, 
                cart, 
                this.usaEpayPaymentSettings.AdditionalFee, 
                this.usaEpayPaymentSettings.AdditionalFeePercentage);
            return result;
        }

        /// <summary>
        /// The get configuration route.
        /// </summary>
        /// <param name="actionName">
        /// The action name.
        /// </param>
        /// <param name="controllerName">
        /// The controller name.
        /// </param>
        /// <param name="routeValues">
        /// The route values.
        /// </param>
        public void GetConfigurationRoute(
            out string actionName, 
            out string controllerName, 
            out RouteValueDictionary routeValues)
        {
            actionName = "Configure";
            controllerName = "PaymentUsaEpay";
            routeValues = new RouteValueDictionary { { "Namespaces", "Nop.Plugin.Payments.UsaEpay.Controllers" }, { "area", null } };
        }

        /// <summary>
        ///     The get controller type.
        /// </summary>
        /// <returns>
        ///     The <see cref="Type" />.
        /// </returns>
        public Type GetControllerType()
        {
            return typeof(PaymentUsaEpayController);
        }

        /// <summary>
        /// The get payment info route.
        /// </summary>
        /// <param name="actionName">
        /// The action name.
        /// </param>
        /// <param name="controllerName">
        /// The controller name.
        /// </param>
        /// <param name="routeValues">
        /// The route values.
        /// </param>
        public void GetPaymentInfoRoute(
            out string actionName, 
            out string controllerName, 
            out RouteValueDictionary routeValues)
        {
            actionName = "PaymentInfo";
            controllerName = "PaymentUsaEpay";
            routeValues = new RouteValueDictionary { { "Namespaces", "Nop.Plugin.Payments.UsaEpay.Controllers" }, { "area", null } };
        }

        /// <summary>
        /// The install.
        /// </summary>
        public override void Install()
        {
            // settings
            var settings = new UsaEpayPaymentSettings
            {
                AdditionalFee = 0,
                AdditionalFeePercentage = false,
                Pin = "1234",
                SourceKey = "456789",
                Url = "https://sandbox.usaepay.com/soap/gate/4D64EBCD"
            };
            this.settingService.SaveSetting(settings);

            // locales
            this.AddOrUpdatePluginLocaleResource("Plugins.Payments.UsaEpay.Notes", "Test.");

            base.Install();
        }

        /// <summary>
        /// Post process payment (used by payment gateways that require redirecting to a third-party URL)
        /// </summary>
        /// <param name="postProcessPaymentRequest">
        /// Payment info required for an order processing
        /// </param>
        public void PostProcessPayment(PostProcessPaymentRequest postProcessPaymentRequest)
        {
            // nothing
        }

        /// <summary>
        /// The process payment.
        /// </summary>
        /// <param name="processPaymentRequest">
        /// The process payment request.
        /// </param>
        /// <returns>
        /// The <see cref="ProcessPaymentResult"/>.
        /// </returns>
        public ProcessPaymentResult ProcessPayment(ProcessPaymentRequest processPaymentRequest)
        {
            var processPaymentResult = new ProcessPaymentResult();

            try
            {
                var customer = this.customerService.GetCustomerById(processPaymentRequest.CustomerId);
                var billingAddress = customer.BillingAddress;

                var parameters = new TransactionRequestObject
                                     {
                                         AccountHolder = processPaymentRequest.CreditCardName,
                                         CreditCardData =
                                             new CreditCardData
                                                 {
                                                     CardExpiration =
                                                         string.Format(
                                                             "{0:D2}{1:D2}",
                                                             processPaymentRequest.CreditCardExpireMonth,
                                                             processPaymentRequest.CreditCardExpireYear),
                                                     CardNumber = processPaymentRequest.CreditCardNumber,
                                                     CardType = processPaymentRequest.CreditCardType,
                                                     CAVV = processPaymentRequest.CreditCardCvv2,
                                                 },
                                         Details =
                                             new TransactionDetail
                                                 {
                                                     Amount = Convert.ToDouble(processPaymentRequest.OrderTotal),
                                                     AmountSpecified = true,
                                                     Invoice = processPaymentRequest.PurchaseOrderNumber
                                                 },
                                         BillingAddress =
                                             new Address
                                                 {
                                                     City = billingAddress.City,
                                                     Company = billingAddress.Company,
                                                     Country = billingAddress.Country.ThreeLetterIsoCode,
                                                     Email = billingAddress.Email,
                                                     Fax = billingAddress.FaxNumber,
                                                     FirstName = billingAddress.FirstName,
                                                     LastName = billingAddress.LastName,
                                                     Phone = billingAddress.PhoneNumber,
                                                     State = billingAddress.StateProvince.Abbreviation,
                                                     Street = billingAddress.Address1,
                                                     Street2 = billingAddress.Address2,
                                                     Zip = billingAddress.ZipPostalCode,
                                                 }
                                     };

                var transactionResponse = this.usaEpayService.runTransaction(this.token, parameters);

                processPaymentResult = TransactionResponseProcessor.ProcessTransaction(transactionResponse, processPaymentResult);
            }
            catch (Exception exception)
            {
                processPaymentResult.AddError(exception.Message);
            }

            return processPaymentResult;
        }

        /// <summary>
        /// The process recurring payment.
        /// </summary>
        /// <param name="processPaymentRequest">
        /// The process payment request.
        /// </param>
        /// <returns>
        /// The <see cref="ProcessPaymentResult"/>.
        /// </returns>
        /// <exception cref="NotImplementedException">
        /// Not implemented at this time.
        /// </exception>
        public ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentRequest processPaymentRequest)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// The refund.
        /// </summary>
        /// <param name="refundPaymentRequest">
        /// The refund payment request.
        /// </param>
        /// <returns>
        /// The <see cref="RefundPaymentResult"/>.
        /// </returns>
        public RefundPaymentResult Refund(RefundPaymentRequest refundPaymentRequest)
        {
            var refundPaymentResult = new RefundPaymentResult
                                          {
                                              NewPaymentStatus =
                                                  refundPaymentRequest.Order.PaymentStatus
                                          };

            try
            {
                string transactionId = refundPaymentRequest.Order.CaptureTransactionId;
                TransactionResponse transactionResponse = this.usaEpayService.refundTransaction(
                    this.token, 
                    transactionId, 
                    Convert.ToDouble(refundPaymentRequest.AmountToRefund));

                TransactionResponseProcessor.ProcessRefund(transactionResponse, refundPaymentResult, refundPaymentRequest.IsPartialRefund);
            }
            catch (Exception exception)
            {
                refundPaymentResult.AddError(exception.Message);
            }

            return refundPaymentResult;
        }

        /// <summary>
        /// The void.
        /// </summary>
        /// <param name="voidPaymentRequest">
        /// The void payment request.
        /// </param>
        /// <returns>
        /// The <see cref="VoidPaymentResult"/>.
        /// </returns>
        public VoidPaymentResult Void(VoidPaymentRequest voidPaymentRequest)
        {
            var voidPaymentResult = new VoidPaymentResult { NewPaymentStatus = voidPaymentRequest.Order.PaymentStatus, };

            try
            {
                string transactionId = voidPaymentRequest.Order.AuthorizationTransactionId;
                bool voided = this.usaEpayService.voidTransaction(this.token, transactionId);
                if (voided)
                {
                    voidPaymentResult.NewPaymentStatus = PaymentStatus.Voided;
                }
                else
                {
                    voidPaymentResult.AddError(string.Format("Void for transaction id '{0}' failed.", transactionId));
                }
            }
            catch (Exception exception)
            {
                voidPaymentResult.AddError(exception.Message);
            }

            return voidPaymentResult;
        }
    }
}